﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Serialization;

namespace DotNet.SerializationDemo.Services
{
    public interface IXmlSerializationService {
        /// <summary>
        /// Serializes an object.
        /// </summary>
        /// <param name="model">The object you want to Serialize</param>
        /// <returns>The serialized xml in the form of a string.</returns>
        String Serialize(Object model);

        /// <summary>
        /// Deserializes an object.
        /// </summary>
        /// <param name="xmlSerializedObject">The object you want to Deserialize</param>
        /// <returns>The deserialized object.</returns>
        T Deserialize<T>(String xmlSerializedObject);

        T Deserialize<T>(XElement xmlSerializedObject);
    }

    public class XmlSerializationService : IXmlSerializationService
    {
        #region Unacceptable Characters
        public static readonly List<int> _unacceptableCharacters = new List<int> {
            133,
            161,
            162,
            163,
            164,
            165,
            166,
            167,
            168,
            169,
            170,
            171,
            172,
            173,
            174,
            175,
            176,
            177,
            178,
            179,
            180,
            181,
            182,
            183,
            184,
            185,
            186,
            187,
            188,
            189,
            190,
            191,
            215,
            247,
            192,
            193,
            194,
            195,
            196,
            197,
            198,
            199,
            200,
            201,
            202,
            203,
            204,
            205,
            206,
            207,
            208,
            209,
            210,
            211,
            212,
            213,
            214,
            216,
            217,
            218,
            219,
            220,
            221,
            222,
            223,
            224,
            225,
            226,
            227,
            228,
            229,
            230,
            231,
            232,
            233,
            234,
            235,
            236,
            237,
            238,
            239,
            240,
            241,
            242,
            243,
            244,
            245,
            246,
            248,
            249,
            250,
            251,
            252,
            253,
            254,
            255
        };
        public static string ReplaceUnacceptableCharacters(string str)
        {
            _unacceptableCharacters.ForEach(charCode => str = str.Replace(Char.ConvertFromUtf32(charCode), "&#" + charCode + ";"));

            str = str.Replace(Char.ConvertFromUtf32(160), @" ")
                .Replace(Char.ConvertFromUtf32(8220), @"&quot;")
                .Replace(Char.ConvertFromUtf32(8221), @"&quot;")
                .Replace(Char.ConvertFromUtf32(8216), "'")
                .Replace(Char.ConvertFromUtf32(8217), "'")
                .Replace(Char.ConvertFromUtf32(8482), @"&#8482;")
                .Replace(Char.ConvertFromUtf32(8230), @"&#133;")
                .Replace(Char.ConvertFromUtf32(65533), @"&#133;")
                .Replace(Char.ConvertFromUtf32(8212), @"-")
                .Replace(Char.ConvertFromUtf32(8211), @"-");
            return str;
        }
        #endregion

        #region ISerializationService Methods
        /// <summary>
        /// Serializes an object.
        /// </summary>
        /// <param name="model">The object you want to Serialize</param>
        /// <returns>The serialized xml in the form of a string.</returns>
        public String Serialize(Object model)
        {
            String xmlSerializedString = null;

            var memoryStream = new MemoryStream();
            var serializer = new XmlSerializer(model.GetType());
            var xmlTextWriter = new XmlTextWriter(memoryStream, Encoding.UTF8);

            serializer.Serialize(xmlTextWriter, model);
            memoryStream = (MemoryStream)xmlTextWriter.BaseStream;
            xmlSerializedString = UTF8ByteArrayToString(memoryStream.ToArray());
            xmlSerializedString = xmlSerializedString.Substring(xmlSerializedString.IndexOf("<"), xmlSerializedString.LastIndexOf(">")); ;
            xmlSerializedString = xmlSerializedString.Replace("<?xml version=\"1.0\" encoding=\"utf-8\"?>", String.Empty); ;
            xmlSerializedString = xmlSerializedString.Replace(" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"", String.Empty);
            xmlSerializedString = xmlSerializedString.Replace(" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"", String.Empty);
            xmlSerializedString = xmlSerializedString.Replace(" xmlns=\"http://CafePress.com/\"", String.Empty);

            xmlSerializedString = ReplaceUnacceptableCharacters(xmlSerializedString);

            return xmlSerializedString;
        }


        /// <summary>
        /// Deserializes an object.
        /// </summary>
        /// <param name="xmlSerializedObject">The object you want to Deserialize</param>
        /// <returns>The deserialized object.</returns>
        public T Deserialize<T>(String xmlSerializedObject)
        {
            T model = default(T);

            var xs = new XmlSerializer(typeof(T));
            var memoryStream = new MemoryStream(StringToUTF8ByteArray(xmlSerializedObject));
            model = (T)xs.Deserialize(memoryStream);

            return model;
        }
        public T Deserialize<T>(XElement xmlSerializedObject)
        {
            return Deserialize<T>(xmlSerializedObject.ToString());
        }
        #endregion

        #region Utility Methods
        /// <summary>
        /// Converts UTF8 Byte Array to a string. This is used in the serialize 
        /// method so I can write to a memory stream instead of a file.
        /// </summary>
        public static String UTF8ByteArrayToString(Byte[] characters)
        {
            var encoding = new UTF8Encoding();
            var constructedString = encoding.GetString(characters);
            return (constructedString);
        }

        /// <summary>
        /// Converts a string to a UTF8 Byte Array. This is This is used in the 
        /// deserialize method so I can read from a memory stream instead of a file.
        /// </summary>
        public static Byte[] StringToUTF8ByteArray(String pXmlString)
        {
            var encoding = new UTF8Encoding();
            var byteArray = encoding.GetBytes(pXmlString);
            return byteArray;
        }
        #endregion
    }
}
